import json
import random
import signal
from threading import Thread, Event
from http import HTTPStatus
from urllib.parse import urlsplit, parse_qs
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
from xmlrpc.server import SimpleXMLRPCServer

from node import Node
from log import LogEntry


class XMLRPCServer(SimpleXMLRPCServer):

    def __init__(self, host, port):
        super().__init__((host, port), allow_none=True)


class HTTPServer(ThreadingHTTPServer):

    def __init__(self, node, *args, **kwargs):
        self.node = node
        super().__init__(*args, **kwargs)


class HTTPRequestHandler(BaseHTTPRequestHandler):

    def __init__(self, *args, **kwargs):
        self._query_params = None
        self._url_path = None
        super().__init__(*args, **kwargs)

    @property
    def node(self) -> Node:
        return self.server.node

    @property
    def query_params(self):
        if self._query_params is None:
            url = urlsplit(self.path)
            qs = parse_qs(url.query)
            self._query_params = {k: v if len(v) > 1 else v[0] for k, v in qs.items()}
        return self._query_params

    def write_response(self, code, content):
        self.send_response(code)
        if isinstance(content, dict):
            self.send_header("Content-type", 'application/json')
            content = json.dumps(content).encode()
        elif isinstance(content, str):
            self.send_header("Content-type", 'text/plain')
            content = content.encode()
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)

    def parse_request(self) -> bool:
        ret = super().parse_request()
        if not ret:
            return ret

        if self.path == '/ping':
            self.write_response(HTTPStatus.OK, 'pong')
            return False

        if self.path == '/info':
            self.write_response(HTTPStatus.OK, self.node.info())
            return False

        if not self.node.ensure_leader():
            content = {'lead_id': self.node.leader_id}
            self.write_response(HTTPStatus.PERMANENT_REDIRECT, content)
            return False

        return True

    def do_GET(self):
        key = self.query_params['key']
        val = self.node.state_machine.get(key)
        self.write_response(HTTPStatus.OK, {'key': key, 'val': val})

    def validate_params(self, data: dict):
        _id = data['id']
        op = data['op']  # set get del
        key = data['key']
        val = data.get('val', '')

        if len(_id) == 0:
            raise ValueError("id cannot be empty")

        if op not in ('set', 'del'):
            raise ValueError('op must be in (set, del)')

        if not 0 < len(key.encode()) <= 128:
            raise ValueError('The length of the key must be in (0, 128]')

        if len(val.encode()) > 512:
            raise ValueError('The length of the val cannot exceed 512 bytes')

    def do_POST(self):
        length = int(self.headers['Content-Length'])
        try:
            data = json.loads(self.rfile.read(length))
        except ValueError:
            self.write_response(HTTPStatus.BAD_REQUEST, 'JSON Format is required')
            return

        try:
            self.validate_params(data)
        except KeyError as e:
            self.write_response(HTTPStatus.BAD_REQUEST, 'Missing required parameters: %s' % e)
            return
        except ValueError as e:
            self.write_response(HTTPStatus.BAD_REQUEST, 'Parameters error: %s' % e)
            return

        entry = LogEntry(**data)
        self.node.append_log(entry)
        self.write_response(HTTPStatus.OK, 'ok')


class RaftServer:

    def __init__(self, ident, addr, remote_servers):
        self.ticker = Event()
        self.node = Node(ident, remote_servers, self.ticker)
        self.host, self.rpc_port, self.http_port = addr
        self.running = True

    def run_httpserver(self):
        self.httpd = HTTPServer(self.node, (self.host, self.http_port), HTTPRequestHandler)
        self.httpd.serve_forever()

    def run_rpc_server(self):
        self.rpc_server = XMLRPCServer(self.host, self.rpc_port)
        self.rpc_server.register_instance(self.node.create_rpc_service())
        self.rpc_server.serve_forever()

    def serve(self):
        t1 = Thread(target=self.run_httpserver, daemon=True)
        t1.start()
        t2 = Thread(target=self.run_rpc_server, daemon=True)
        t2.start()

        while self.running:
            timeout = random.randint(150, 300)/1000.0
            if self.ticker.wait(timeout):
                self.ticker.clear()
            else:
                self.node.request_vote()

        self.httpd.shutdown()
        self.rpc_server.shutdown()
        t1.join()
        t2.join()

    def shutdown(self):
        self.running = False


def sig_handler(func):
    def _hdr(sig, frame):
        func()
    signal.signal(signal.SIGTERM, _hdr)
    signal.signal(signal.SIGINT, _hdr)


def main(ident):
    servers = {
        's1': ('127.0.0.1', 8101, 9101),
        's2': ('127.0.0.1', 8102, 9102),
        's3': ('127.0.0.1', 8103, 9103),
    }
    addr = servers.pop(ident)

    rs = RaftServer(ident, addr, servers)
    sig_handler(rs.shutdown)
    rs.serve()


if __name__ == '__main__':
    import sys
    main(sys.argv[1])
